﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Bamboo.Utils;

namespace Bamboo.ExeShell
{
    public class PuttyBase
    {
        /// <summary>
        /// 主机名或IP地址
        /// </summary>
        public string Hostname { get; set; } = "";
        /// <summary>
        /// 用户名
        /// </summary>
        public string Username { get; set; } = "";
        /// <summary>
        /// 密码
        /// </summary>
        public string Password { get; set; } = "";

        public SSHAuthenticationEnum SSHAuthenticationType { get; set; } = PuttyBase.SSHAuthenticationEnum.密码;

        public string PrivateKeyPath = "";

        /// <summary>
        /// ssh登录验证方式
        /// </summary>
        public enum SSHAuthenticationEnum
        {
            密码 = 1,//使用密码
            秘钥 = 2 //使用公钥私钥验证的方式
        };

        /// <summary>
        /// SSH协议端口, 默认为22, 一般不用修改
        /// </summary>

        public int Port = 22;
        /// <summary>
        /// 是否忽略错误: true-发生错误时无视; false-发生错误时抛出异常
        /// 默认为true
        /// </summary>
        public bool IgnoreError { get => Cmd.IgnoreError; set => Cmd.IgnoreError = value; }

        /// <summary>
        /// 命令行的字符集,默认为gbk
        /// </summary>
        public string CommandEncoding { get => Cmd.EncodingName; set => Cmd.EncodingName = value; }
        /// <summary>
        /// 超时时间(秒), 默认为3600秒.
        /// </summary>
        public int TimeoutSeconds { get => Cmd.TimeoutSeconds; set => Cmd.TimeoutSeconds = value; }

        public string OutFilename { get => Cmd.OutputFileName; set => Cmd.OutputFileName = value; }
        public string ErrorFilename { get => Cmd.ErrorFileName; set => Cmd.ErrorFileName = value; }

        /// <summary>
        /// 保存上次执行结束后的命令行对象
        /// </summary>
        public ProcessExecute Cmd { get; } = new ProcessExecute();

        public event Action<string> LogHandler { add => Cmd.OutputHandler += value; remove => Cmd.OutputHandler -= value; }
        public event Action<string> ErrorHandler { add => Cmd.ErrorHandler += value; remove => Cmd.ErrorHandler -= value; }

        public PuttyBase()
        {
            IgnoreError = true;
            CommandEncoding = "gbk";
            TimeoutSeconds = 3600;
        }

        protected void Validate()
        {
            if (string.IsNullOrWhiteSpace(Hostname))
                throw new Exception("主机名未配置");
            if (string.IsNullOrWhiteSpace(Username))
                throw new Exception("用户名未配置");
            if (SSHAuthenticationType == PuttyBase.SSHAuthenticationEnum.密码)
            {
                if (string.IsNullOrWhiteSpace(Password))
                    throw new Exception("密码未配置");
            }
            else
            {
                if (!File.Exists(PrivateKeyPath))
                    throw new Exception($"选择使用秘钥方式验证，但是私钥路径：{PrivateKeyPath}不存在");
            }
          
        }

        protected void Log(string s)
        {
            Cmd.OutputHandler?.Invoke(s);
        }

        protected void LogError(string s)
        {
            Cmd.ErrorHandler?.Invoke(s);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="c"></param>
        /// <param name="command">批处理命令</param>
        protected void CheckAndRun(ProcessExecute c, string filename, string argument)
        {
            Validate();
            try
            {
                c.FileName = filename;
                c.Arguments = argument;
                c.Run();
                if (NeedSaveHostKey(c.ErrorText) || NeedSaveHostKey(c.ForceSuccessText))
                {
                    if (AutoFixHostKeyError(Hostname, Port))
                    {
                        Log("自动保存Putty的HostKey成功，重新执行一次脚本。");
                        c.Run();
                    }
                }
            }
            catch (Exception ex)
            {
                if (NeedSaveHostKey(c.ErrorText) || NeedSaveHostKey(c.ForceSuccessText))
                {
                    if (AutoFixHostKeyError(Hostname, Port))
                    {
                        Log("自动保存Putty的HostKey成功，重新执行一次脚本。");
                        c.Run();
                    }
                    else
                    {
                        throw new Exception("自动保存Putty的HostKey失败", ex);
                    }
                }
                else
                {
                    throw;
                }
            }
        }

        public static bool NeedSaveHostKey(StringBuilder errorMessage)
        {
            int len = 4096 > errorMessage.Length ? errorMessage.Length : 4096;
            string text = errorMessage.ToString(0, len);
            if (text.Contains("The server's host key") || text.Contains("changed the host key") || text.Contains("The host key is not cached") || text.Contains("The host key does not match the")) return true;
            else return false;
        }

        public static bool AutoFixHostKeyError(string hostname, int port)
        {
            var c = new ProcessExecute();
            c.IgnoreError = true;
            c.TimeoutSeconds = 10;
            c.RunBat($"echo Y|plink -ssh -P {port} {hostname} exit");
            //putty 0.73版本的ExitCode == 0；0.81版本会报错: FATAL ERROR: Error reading from console: Error 109: 管道已结束。 但是实际上已经保存密钥成功了，只是这次执行报错
            bool succ = c.ExitCode == 0  || c.OutputText.ToString().Contains("Error 109");
            return succ;
        }

        public static bool IsHostKeyError_del(StringBuilder errorBuffer)
        {
            if (errorBuffer.Length == 0) return false;
            //取前128个字节足够了
            int length = (errorBuffer.Length > 128) ? 128 : errorBuffer.Length;
            string text = errorBuffer.ToString(0, length);
            var ret = (text.Contains("The server's host key") ||
                text.Contains("changed the host key") || text.Contains("The host key is not cached")|| text.Contains("The host key does not match the"));
            return ret;
        }

        public void FixHostKeyError_del()
        {
            var c = new ProcessExecute();
            c.TimeoutSeconds = 20;
            c.OutputHandler += Log;
            c.ErrorHandler += LogError;
            c.EncodingName = this.CommandEncoding;
            c.RunBat($"echo Y|plink -ssh -P {Port} {Username}@{Hostname} exit");
        }
    }
}

//自动注册PUTTY HOSTKEY：
//echo Y|plink -ssh dev@192.168.205.90 exit

/*
c:\svn\LSFM2\trunk\dev\devtool\Bin\Westlake>plink.exe -ssh 192.168.205.90
The server's host key is not cached in the registry. You
have no guarantee that the server is the computer you
think it is.
The server's rsa2 key fingerprint is:
ssh-rsa 2048 a0:95:f5:5f:90:a7:e8:df:7d:cd:6a:9e:14:95:9a:9c
If you trust this host, enter "y" to add the key to
PuTTY's cache and carry on connecting.
If you want to carry on connecting just once, without
adding the key to the cache, enter "n".
If you do not trust this host, press Return to abandon the
connection.
    */

/*
一个更新key的运行例子:
C:\work\LSFM2\trunk\release\研发管理工具>plink.exe -ssh 192.168.1.211
WARNING - POTENTIAL SECURITY BREACH!
The server's host key does not match the one PuTTY has
cached in the registry. This means that either the
server administrator has changed the host key, or you
have actually connected to another computer pretending
to be the server.
The new rsa2 key fingerprint is:
ssh-rsa 2048 a0:95:f5:5f:90:a7:e8:df:7d:cd:6a:9e:14:95:9a:9c
If you were expecting this change and trust the new key,
enter "y" to update PuTTY's cache and continue connecting.
If you want to carry on connecting but without updating
the cache, enter "n".
If you want to abandon the connection completely, press
Return to cancel. Pressing Return is the ONLY guaranteed
safe choice.
Update cached key? (y/n, Return cancels connection) Connection abandoned.
     
     */
